#include "graphics.h"
#include "game.h"
#include <dxerr.h>
#pragma comment(lib, "d3d9.lib")
#pragma comment(lib, "dxerr.lib")

#ifndef COM_RELEASE
#define COM_RELEASE(iface) if(iface) { iface->Release(); iface = NULL; }
#endif

bool Graphics::initialize()
{
	if(d3d || device)
		close();
	
	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if(!d3d)
		return false;

	HRESULT hr;
	D3DPRESENT_PARAMETERS d3dpp; 
	ZeroMemory( &d3dpp, sizeof(d3dpp) );
	d3dpp.Windowed = TRUE;
	d3dpp.SwapEffect = D3DSWAPEFFECT_COPY;
	d3dpp.EnableAutoDepthStencil = 1;
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
	if(FAILED(hr = d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
		Game::getWindowHandle(), D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &device)))
	{
		COM_RELEASE(d3d);
		return false;
	}


	D3DCAPS9 caps;
	memset(&caps, 0, sizeof(D3DCAPS9));
	if(SUCCEEDED(device->GetDeviceCaps(&caps)))
	{
		char buf[64];
		sprintf(buf, "PixelShader v%d.%d\n",
			D3DSHADER_VERSION_MAJOR(caps.PixelShaderVersion),
			D3DSHADER_VERSION_MINOR(caps.PixelShaderVersion));
		OutputDebugString(buf);
	}

	if(FAILED(hr = device->GetRenderTarget(0, &renderTarget)))
	{
		COM_RELEASE(device);
		COM_RELEASE(d3d);
		return false;
	}

	// standardowe ustawienia Direct3D
	device->SetRenderState(D3DRS_LIGHTING, 1);
	device->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_RGBA(255, 255, 255, 255));
	device->SetRenderState(D3DRS_ALPHABLENDENABLE, 1);
	device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	device->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	device->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE);

	// wlacz filtrowanie trojliniowe
	device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

	// domyslne transformacje
	setWorldTransformation(Matrix::identity());
	setViewTransformation(Matrix::identity());
	setProjectionTransformation(Matrix::projectionPerspective(
		3.1415926f / 4.0f, 4.0f / 3.0f, 0.1f, 1000.0f));

	// kamera
	camera = new Camera();
	
	// czcionka
	fontImage = (Image*)Game::getResourceManager()->addResource("images/fontverdana18.png", Resource::ImageResource);
	if(!fontImage)
		return false;
	fontMetrics = (RawDataResource*)Game::getResourceManager()->addResource("images/fontverdana18metrics.dat", Resource::RawDataResource);
	if(!fontMetrics)
		return false;

	return true;
}

void Graphics::close()
{
	if(fontMetrics)
		fontMetrics->release();
	if(fontImage)
		fontImage->release();
	if(camera)
		delete camera;

	COM_RELEASE(renderTarget);
	COM_RELEASE(device);
	COM_RELEASE(d3d);
}

bool Graphics::beginDrawing(bool clear) const
{
	HRESULT hr;

	if(!device)
		return false;

	if(clear)
		if(FAILED(hr = device->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
				D3DCOLOR_XRGB(143, 169, 214), 1.0f, 0)))
			return false;

	return true;
}

void Graphics::endDrawing() const
{
	if(!device)
		return;
	device->Present(NULL, NULL, NULL, NULL);
}

void Graphics::beginScene() const
{
	camera->animationStep();
	setViewTransformation(camera->getAnimatedTransformation());
	device->BeginScene();
}

void Graphics::endScene() const
{
	device->EndScene();
}

void Graphics::drawImage(const Image &image) const
{
	device->StretchRect(image.getSurface(), NULL, renderTarget, NULL, D3DTEXF_NONE);
}

void Graphics::drawImage(const Image &image, int x, int y) const
{
	RECT destRect;
	destRect.left = x;
	destRect.top = y;
	destRect.right = x + image.getWidth();
	destRect.bottom = y + image.getHeight();
	device->StretchRect(image.getSurface(), NULL, renderTarget, &destRect, D3DTEXF_NONE);
}

void Graphics::drawImage(const Image &image, int x, int y, float width, float height) const
{
	RECT destRect;
	destRect.left = x;
	destRect.top = y;
	destRect.right = x + (LONG)(image.getWidth() * width);
	destRect.bottom = y + (LONG)(image.getHeight() * height);
	device->StretchRect(image.getSurface(), NULL, renderTarget, &destRect, D3DTEXF_NONE);
}

void Graphics::draw2DTexture(const Texture &tex, float x, float y, float w, float h, BYTE r, BYTE g, BYTE b, BYTE a) const
{
	struct vertex2d
	{
		float x, y, z, w;
		D3DCOLOR diffuse;
		float u, v;
	};
	D3DCOLOR diffuse = D3DCOLOR_ARGB(a, r, g, b);
	vertex2d vertices[] =
	{
		{ x    , y    , 0, 1, diffuse, 0, 0 },
		{ x + w, y    , 0, 1, diffuse, 1, 0 },
		{ x + w, y + h, 0, 1, diffuse, 1, 1 },
		{ x    , y + h, 0, 1, diffuse, 0, 1 }
	};
	static short int indices[] =
	{
		0, 1, 2, 3
	};
	device->SetFVF(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1);
	device->SetTexture(0, tex.getTexture());
	//device->SetRenderState(D3DRS_ZENABLE, FALSE);

	device->DrawIndexedPrimitiveUP(D3DPT_TRIANGLEFAN, 0, 4, 2,
		indices, D3DFMT_INDEX16, vertices, sizeof(vertex2d));

	//device->SetRenderState(D3DRS_ZENABLE, TRUE);

}

void Graphics::drawWireframeBox(const Vector &min, const Vector &max) const
{
	/*
		Kolejnosc wierzcholkow:

			  /3--------/7
			 /  |      / |
			/   |     /  |
			1---------5  |
			|   2- - -| -6
			|  /      |  /
			|/        | /
			0---------4/

		Wierzcholek 0 = (0, 0, 0)
		Wierzcholek 7 = (1, 1, 1)
	*/
	static float boxVertices[] = 
	{
		0.0f, 0.0f, 0.0f,
		0.0f, 1.0f, 0.0f,
		0.0f, 0.0f, 1.0f,
		0.0f, 1.0f, 1.0f,
		1.0f, 0.0f, 0.0f,
		1.0f, 1.0f, 0.0f,
		1.0f, 0.0f, 1.0f,
		1.0f, 1.0f, 1.0f
	};
	static short int boxIndices[] = 
	{
		0, 4,
		4, 6,
		6, 2,
		2, 0,
		1, 5,
		5, 7,
		7, 3,
		3, 1,
		0, 1,
		4, 5,
		6, 7,
		2, 3
	};

	D3DMATRIX oldD3DMatrix;
	device->GetTransform(D3DTS_WORLD, &oldD3DMatrix);
/*	Matrix boxMatrix(&oldD3DMatrix);*/
	Matrix boxMatrix;
	Matrix trans, scale;
	trans.setTranslation(min);
	scale.setScale(max - min);
	boxMatrix = boxMatrix * trans * scale;
	setWorldTransformation(boxMatrix);

	D3DMATERIAL9 material;
	memset(&material, 0, sizeof(D3DMATERIAL9));
	material.Ambient.r = 255;
	material.Ambient.g = 255;
	material.Ambient.b = 255;
	material.Ambient.a = 255;
	material.Diffuse = material.Ambient;
	material.Power = 1.0f;

	device->SetFVF(D3DFVF_XYZ);
	device->SetMaterial(&material);
	device->SetTexture(0, NULL);
	device->SetRenderState(D3DRS_ZENABLE, FALSE);

	device->DrawIndexedPrimitiveUP(D3DPT_LINELIST, 0, 8, 12,
		boxIndices, D3DFMT_INDEX16, boxVertices, 3*sizeof(float));

	device->SetRenderState(D3DRS_ZENABLE, TRUE);
	setWorldTransformation(Matrix(&oldD3DMatrix));
}

void Graphics::drawWireframeTriangle(const Triangle &tri) const
{
	const Vector* v = tri.getVertices();
	Vector n = tri.getPlane().getNormal();
	n *= 0.1f;
	float vertices[] = 
	{
		v[0].getX(), v[0].getY(), v[0].getZ(),
		v[1].getX(), v[1].getY(), v[1].getZ(),
		v[2].getX(), v[2].getY(), v[2].getZ(),
		v[0].getX() + n.getX(), v[0].getY() + n.getY(), v[0].getZ() + n.getZ(),
		v[1].getX() + n.getX(), v[1].getY() + n.getY(), v[1].getZ() + n.getZ(),
		v[2].getX() + n.getX(), v[2].getY() + n.getY(), v[2].getZ() + n.getZ()
	};
	static short int indices[] = 
	{
		0, 1,
		1, 2,
		2, 0,
		0, 3,
		1, 4,
		2, 5,
	};

	D3DMATRIX oldD3DMatrix;
	device->GetTransform(D3DTS_WORLD, &oldD3DMatrix);
	setWorldTransformation(Matrix().identity());

	D3DMATERIAL9 material;
	memset(&material, 0, sizeof(D3DMATERIAL9));
	material.Ambient.r = 255;
	material.Ambient.g = 255;
	material.Ambient.b = 255;
	material.Ambient.a = 255;
	material.Diffuse = material.Ambient;
	material.Power = 1.0f;

	device->SetFVF(D3DFVF_XYZ);
	device->SetMaterial(&material);
	device->SetTexture(0, NULL);
	device->SetRenderState(D3DRS_ZENABLE, FALSE);

	device->DrawIndexedPrimitiveUP(D3DPT_LINELIST, 0, 6, 6,
		indices, D3DFMT_INDEX16, vertices, 3*sizeof(float));

	device->SetRenderState(D3DRS_ZENABLE, TRUE);
	setWorldTransformation(Matrix(&oldD3DMatrix));
}

void Graphics::drawText(int x, int y, const std::string &text)
{
	drawText(x, y, text.c_str());
}

void Graphics::drawText(int x, int y, const char* text)
{
	if(!fontImage)
		return;

	RECT srcRect, destRect;
	float dx = fontImage->getWidth() / 16.0f, dy = fontImage->getHeight() / 16.0f;
	short* metrics = (short*)fontMetrics->getData();
	char c;
	while(c = *text++)
	{
		srcRect.left = (int)(((c % 16) * dx) + (dx/2 - metrics[c]/2));
		srcRect.top = (int)((c / 16) * dy);
		srcRect.right = (int)(srcRect.left + metrics[c]);
		srcRect.bottom = (int)(srcRect.top + dy);

		destRect.left = x;
		destRect.top = y;
		destRect.right = (int)(x + metrics[c]);
		destRect.bottom = (int)(y + dy);

		device->StretchRect(fontImage->getSurface(),
			&srcRect, renderTarget, &destRect, D3DTEXF_NONE);

		x += (int)metrics[c] + 2;
	}
}

void Graphics::setWorldTransformation(const Matrix &mtx) const
{
	device->SetTransform(D3DTS_WORLD, mtx);
	/*
	world = mtx;
	worldViewProj = world * view * proj;
	*/
}

void Graphics::setViewTransformation(const Matrix &mtx) const
{
	device->SetTransform(D3DTS_VIEW, mtx);
	/*
	view = mtx;
	worldViewProj = world * view * proj;
	*/
}

void Graphics::setProjectionTransformation(const Matrix &mtx) const
{
	device->SetTransform(D3DTS_PROJECTION, mtx);
	/*
	proj = mtx;
	worldViewProj = world * view * proj;
	*/
}
